/*
 * RegExpConceptType.java
 *
 * Created on 1 April 2007, 19:47
 *
 */

package rxtokenizer;

import java.util.*;

/**
 * Concept type based on regular expressions.
 * @author Ruslan Fayzrakhmanov
 */
public class RegExpConceptType extends ConceptType {
    
    RegExpConceptType(String name, String originalExtractingExp) {
	super(name, originalExtractingExp);
    }
    RegExpConceptType(String name, String originalExtractingExp, String description) {
        super(name, originalExtractingExp, description);
    }
    RegExpConceptType(String name, String originalExtractingExp, String constraintExtractingExp, String description) {
        super(name, originalExtractingExp, constraintExtractingExp, description);
    }

	/**
	 * Changing name of the concept and in the extracting expressions of derived concepts.
	 * @param newName 
	 * @return Result
	 */
    final Result changeName(String newName) {
	Result res;
	if (derivedConceptTypes!=null) {
	    Iterator conceptTypeIter = derivedConceptTypes.iterator();
	    while (conceptTypeIter.hasNext()) {
		res = ((RegExpConceptType)conceptTypeIter.next()).changeNameInExtrExp(newName, name);
		if (res!=Result.NO_ERROR) return res;
	    }
	}
	name = newName;
	if (constraintExtractingExp.length() > 0 ) {
	    res = changeNameInConstraintExp(newName);
	    if (res!=Result.NO_ERROR) return res;
	}
	return Result.NO_ERROR;
    }
    /**
	 * This function is necessary for the function changeName(String).
	 * Changing old name of the concept type in extracting expression on new name.
	 * @param newName 
	 * @param oldName 
	 * @return Result
	 */
    private final Result changeNameInExtrExp(String newName, String oldName) {
	oldName = oldName.toLowerCase();
	String extrExp = originalExtractingExp;
	StringBuffer newExtrExp = new StringBuffer();
	StringBuffer name = new StringBuffer();
	byte state = 0;
	char ch;
	for (int i=0; i<extrExp.length(); i++) {
	    ch=extrExp.charAt(i);
	    switch (state) {
		// Outside concept declaration
		case 0: 
		    if (isConceptTypeBorder(ch))
			state = 1;
		    newExtrExp.append(ch);
		    break;
		    // Inside concept declaration
		case 1:
		    // Symbol '/'
		    if (isConceptTypeBorder(ch)) {
			newExtrExp.append(ch);
			state = 0;
		    }
		    // First symbol of the concept name
		    else if (isConceptNameFirstSymbol(ch)) {
			name.append(ch);
			state = 2;
		    }
		    else
			return Result.ERROR_13;
		    break;
		case 2:
		    // Anther symbol in concept name
		    if (isConceptNameFollowSymbol(ch))
			name.append(ch);
		    // Concept name was readed
		    else if ( (ch == ' ')||(isConceptTypeBorder(ch)) ) {
			if (name.toString().toLowerCase().equals(oldName))
			    newExtrExp.append(newName);
			else
			    newExtrExp.append(name);
			name.setLength(0);
			newExtrExp.append(ch);
			if (ch == ' ')
			    state = 3;
			else if (isConceptTypeBorder(ch))
			    state = 0;
		    }
		    else
			return Result.ERROR_13;
		    break;
		case 3:
		    // First symbol of the attribute name
		    if (isAttributeNameSymbol(ch))
			state = 4;
		    else
			return Result.ERROR_14;
		    newExtrExp.append(ch);
		    break;
		case 4:
		    // Anther symbol of the attribute name
		    if (isAttributeNameSymbol(ch))
			;
		    // Symbol '='
		    else if (ch == '=')
			state=5;
		    else
			return Result.ERROR_14;
		    newExtrExp.append(ch);
		    break;
		case 5:
		    // Value of the attribute is begun to read
		    if (ch == '"')
			state = 6;
		    else
			return Result.ERROR_15;
		    newExtrExp.append(ch);
		    break;
		case 6:
		    // Value of the attribute was readed
		    if (ch == '"')
			state = 7;
		    // Values of the attribute
		    else 
			;
		    newExtrExp.append(ch);
		    break;
		case 7:
		    // Symbol ' '
		    if (ch == ' ')
			state = 3;
		    else if (isConceptTypeBorder(ch))
			state = 0;
		    newExtrExp.append(ch);
	    }
	}
	if (state==0) {
	    originalExtractingExp = newExtrExp.toString();
	    return Result.NO_ERROR;
	}
	else
	    return Result.ERROR_16;
    }

	/**
	 * Checking structure of the extracting expression.
	 * @return Result
	 */
    final Result checkExtractingExp() {
	return checkExtractingExp(originalExtractingExp);
    }
    
	/**
	 * Checking structure of the extracting expression.
	 * @param extrExp 
	 * @return Result
	 */
    public final static Result checkExtractingExp(String extrExp) {
	if (extrExp.length() == 0) return Result.ERROR_25;
	byte state = 0;
	char ch;
	for (int i=0; i<extrExp.length(); i++) {
	    ch=extrExp.charAt(i);
	    switch (state) {
		// Outside concept declaration
		case 0: 
		    if (isConceptTypeBorder(ch))
			state = 1;
		    break;
		    // Inside concept declaration
		case 1:
		    // Symbol '/'
		    if (isConceptTypeBorder(ch))
			state = 0;
		    // First symbol of the concept name
		    else if (isConceptNameFirstSymbol(ch))
			state = 2;
		    else
			return Result.ERROR_13;
		    break;
		case 2:
		    // Anther symbol in concept name
		    if (isConceptNameFollowSymbol(ch))
			;
		    // Concept name was readed
		    else if (ch == ' ')
			state = 3;
		    else if (isConceptTypeBorder(ch))
			state = 0;
		    else
			return Result.ERROR_13;
		    break;
		case 3:
		    // First symbol of the attribute name
		    if (isAttributeNameSymbol(ch))
			state = 4;
		    else
			return Result.ERROR_14;
		    break;
		case 4:
		    // Anther symbol of the attribute name
		    if (isAttributeNameSymbol(ch))
			;
		    // Symbol '='
		    else if (ch == '=')
			state=5;
		    else
			return Result.ERROR_14;
		    break;
		case 5:
		    // Value of the attribute is begun to read
		    if (ch == '"')
			state = 6;
		    else
			return Result.ERROR_15;
		    break;
		case 6:
		    // Value of the attribute was readed
		    if (ch == '"')
			state = 7;
		    // Values of the attribute
		    else 
			;
		    break;
		case 7:
		    // Symbol ' '
		    if (ch == ' ')
			state = 3;
		    else if (isConceptTypeBorder(ch))
			state = 0;
		    else return Result.ERROR_23;
	    }
	}
	if (state==0)
	    return Result.NO_ERROR;
	else
	    return Result.ERROR_16;
    }

	/**
	 * 
	 * @param conceptContainer 
	 * @param checkRecursiveDependence 
	 * @return 
	 */
    public final Result fullCheckExtractingExp(ConceptTypeContainer conceptContainer, boolean checkRecursiveDependence) {
	return fullCheckExtractingExp(originalExtractingExp, conceptContainer, checkRecursiveDependence);
    }
	/**
	 * Full check the extracting expression:
	 * 1. Checking structure.
	 * 2. Checking used concepts (checking for their existence and dependences).
	 * 3. Checking used attributes.
	 * @param newExtrExp 
	 * @param conceptContainer 
	 * @param checkRecursiveDependence 
	 * @return 
	 */
    public final Result fullCheckExtractingExp(String newExtrExp, ConceptTypeContainer conceptContainer, boolean checkRecursiveDependence) {
	Result res = Result.NO_ERROR;
	String extrExpTmp = originalExtractingExp;
	if (checkRecursiveDependence)
	     changeExtractingExp(newExtrExp, conceptContainer);
	else
	    originalExtractingExp = newExtrExp;
	res = fullCheckExtractingExpRecursively(conceptContainer, checkRecursiveDependence);
	if (checkRecursiveDependence)
	     changeExtractingExpBack(extrExpTmp, conceptContainer);
	else
	    originalExtractingExp = extrExpTmp;
	return res;
    }
    
	/**
	 * 
	 * @param conceptContainer 
	 * @param checkRecursiveDependence 
	 * @return 
	 */
    private final Result fullCheckExtractingExpRecursively(ConceptTypeContainer conceptContainer, boolean checkRecursiveDependence) {
	if (originalExtractingExp.length() == 0) return Result.ERROR_25;
	if (isVisitedInRecursion)
	    return Result.ERROR_20;
	isVisitedInRecursion = true;
	Result res = parseFullCheck(conceptContainer, checkRecursiveDependence);
	isVisitedInRecursion = false;
	return res;
    }
	/**
	 * 
	 */
    private final Result parseFullCheck(ConceptTypeContainer conceptContainer, boolean checkRecursiveDependence) {
	String extrExp = originalExtractingExp;
	StringBuffer name = new StringBuffer();
	ConceptType conceptTypeTmp;
	StringBuffer attrName = new StringBuffer();
	StringBuffer attrValue = new StringBuffer();
	
	byte state = 0;
	char ch;
	for (int i=0; i<extrExp.length(); i++) {
	    ch=extrExp.charAt(i);
	    switch (state) {
		// Outside concept declaration
		case 0: 
		    if (isConceptTypeBorder(ch))
			state = 1;
		    break;
		    // Inside concept declaration
		case 1:
		    // Symbol '/'
		    if (isConceptTypeBorder(ch))
			state = 0;
		    // First symbol of the concept name
		    else if (isConceptNameFirstSymbol(ch)) {
			name.append(ch);
			state = 2;
		    }
		    else
			return Result.ERROR_13;
		    break;
		case 2:
		    // Anther symbol in concept name
		    if (isConceptNameFollowSymbol(ch))
			name.append(ch);
		    // Concept name was readed
		    else if ( (ch == ' ')||isConceptTypeBorder(ch) ) {
			conceptTypeTmp = conceptContainer.getConceptTypeByName(name.toString());
			if (conceptTypeTmp==null)
			    return Result.ERROR_17;
			else if (!(conceptTypeTmp instanceof RegExpConceptType))  
			    return Result.ERROR_18;
			else {
			    if (!conceptTypeTmp.isExtrExpParsed) {
				if (checkRecursiveDependence) {
				    Result resultTmp = ((RegExpConceptType)conceptTypeTmp).fullCheckExtractingExpRecursively(conceptContainer,checkRecursiveDependence);
				    if (resultTmp != Result.NO_ERROR) return resultTmp;
				} else
				    return Result.ERROR_19;
			    }
			    name.setLength(0);
			
			if (ch == ' ')
			    state = 3;
			else if (isConceptTypeBorder(ch))
			    state = 0;
			}
		    }
		    else
			return Result.ERROR_13;
		    break;
		case 3:
		    // First symbol of the attribute name
		    if (isAttributeNameSymbol(ch)) {
			attrName.append(ch);
			state = 4;
		    }
		    else
			return Result.ERROR_14;
		    break;
		case 4:
		    // Anther symbol of the attribute name
		    if (isAttributeNameSymbol(ch))
			attrName.append(ch);
		    // Symbol '='
		    else if (ch == '=')
			state=5;
		    else
			return Result.ERROR_14;
		    break;
		case 5:
		    // Value of the attribute is begun to read
		    if (ch == '"')
			state = 6;
		    else
			return Result.ERROR_15;
		    break;
		case 6:
		    // Value of the attribute was readed
		    if (ch == '"') {
			Result res = ExtractiveConcept.checkAttribute(attrName.toString(), attrValue.toString());
			if (res == Result.NO_ERROR) {
			    attrName.setLength(0); attrValue.setLength(0);
			    state = 7;
			}
			else return res;
		    }
		    // Values of the attribute
		    else 
			attrValue.append(ch);
		    break;
		case 7:
		    // Symbol ' '
		    if (ch == ' ')
			state = 3;
		    else if (isConceptTypeBorder(ch))
			state = 0;
		    else return Result.ERROR_23;
	    }
	}
	if (state==0)
	    return Result.NO_ERROR;
	else
	    return Result.ERROR_16;
    }
        
	/**
	 * Changing originalExtractingExp of the concept and setting isExtrExpParsed = false in this object and in all derived objects in tree.
	 *
	 * @param newExtrExp 
	 * @param conceptContainer 
	 * @return Result
	 */
    final Result changeExtractingExp(String newExtrExp, ConceptTypeContainer conceptContainer) {
	setIsExtrExpParsedRecursively(this, false);
	originalExtractingExp = newExtrExp;
	return Result.NO_ERROR;
    }
	/**
	 * 
	 * @param newExtrExp 
	 * @param conceptContainer 
	 * @return 
	 */
    private final Result changeExtractingExpBack(String newExtrExp, ConceptTypeContainer conceptContainer) {
	setIsExtrExpParsedRecursively(this, true);
	originalExtractingExp = newExtrExp;
	return Result.NO_ERROR;
    }
	/**
	 * 
	 */
    private final void setIsExtrExpParsedRecursively (ConceptType conceptType, boolean set) {
	conceptType.isExtrExpParsed = set;
	if ( (conceptType.derivedConceptTypes != null)&&(conceptType.derivedConceptTypes.size()>0) ) {
	    Iterator conceptTypeIter = conceptType.derivedConceptTypes.iterator();
	    ConceptType ctTmp;
	    while (conceptTypeIter.hasNext()) {
		ctTmp = (ConceptType)conceptTypeIter.next();
		if (ctTmp.isExtrExpParsed != set)
		    setIsExtrExpParsedRecursively(ctTmp, set);
	    }
	}
    }

    /**
	 * Parsing originalExtractingExp.
	 * 1. Delete all feedbacks with used (base) concept types
	 * 2. Runing parseApply.
	 * 3. Setting feedbacks.
	 * 4. Setting IsExtrExpParsed=false in derived concept types.
	 * @param conceptContainer 
	 * @param withBaseConceptTypes - for recursive applying
	 * @return 
	 */
    final Result parseExtractingExp(ConceptTypeContainer conceptContainer, boolean withBaseConceptTypes) {
	if (isVisitedInRecursion)
	    return Result.ERROR_20;
	isVisitedInRecursion = true;
	removeFeedbacks();
	Result res = parseApply(conceptContainer, withBaseConceptTypes);
	if (res == Result.NO_ERROR)
	    setFeedbacks();
	else
	    return res;
	isVisitedInRecursion = false;
	return res;
    }
    
    /**
	 * 1. Generation of the regular expression for extracting concepts of this token type.
	 * 2. Filling list of used in extracting expression concept types.
	 * 3. Filling list of constraints (attributes).
	 * 4. Counting of the groupQntInRegExp.
	 * 5. Setting isExtrExpParsed=true.
	 * @param conceptContainer 
	 * @param isFullApply - for recursive applying
	 * @return 
	 */
    private final Result parseApply(ConceptTypeContainer conceptContainer, boolean isFullApply) {
	String extrExp = originalExtractingExp;
	if (extrExp.length() == 0) return Result.ERROR_25;
	ExtractiveConcept constraint = null; // Initialised in loop many times
	ExtractiveConcept emptyConstraint = null; // Initialised in loop many times.
	LinkedList constraints = new LinkedList();
	ConceptTypeContainer baseConceptTypes = new ConceptTypeContainer();
	StringBuffer regExp = new StringBuffer();
	int groupQntInRegExp = 0; // Quantity of the open parenthesis for group creating
	int slashQnt = 0; // Quantity of the slash just before open parenthesis
	
	StringBuffer name = new StringBuffer();
	ConceptType conceptTypeTmp = null;
	StringBuffer attrName = new StringBuffer();
	StringBuffer attrValue = new StringBuffer();

	int startBorder = -1;
	int endBorder = -1;
	
		
	byte state = 0;
	char ch;
	for (int i=0; i<extrExp.length(); i++) {
	    ch=extrExp.charAt(i);
	    switch (state) {
		// Outside concept declaration
		case 0:
		    if (isConceptTypeBorder(ch)) {
			startBorder = i;
			state = 1;
		    }
		    else {
			regExp.append(ch);
			// Counting quantity of slashQnt and groupQntInRegExp
			if (ch == '\\') slashQnt++;
			else if (ch == '(') {
			    if ( (slashQnt == 0)&&(slashQnt%2 == 0) )
				groupQntInRegExp++;
			}
		    }
		    if ( (ch != '\\') && (slashQnt > 0) ) slashQnt = 0;
		    break;
		// Inside concept declaration
		case 1:
		    // Symbol '/'
		    if (isConceptTypeBorder(ch)) {
			regExp.append(ch);
			state = 0;
		    }
		    // First symbol of the concept name
		    else if (isConceptNameFirstSymbol(ch)) {
			name.append(ch);
			groupQntInRegExp++;
			state = 2;
		    }
		    else
			return Result.ERROR_13;
		    break;
		case 2:
		    // Anther symbol in concept name
		    if (isConceptNameFollowSymbol(ch))
			name.append(ch);
		    // Concept name was readed
		    else if ( (ch == ' ')||isConceptTypeBorder(ch) ) {
			conceptTypeTmp = conceptContainer.getConceptTypeByName(name.toString());
			if (conceptTypeTmp==null)
			    return Result.ERROR_17;
			else if (!(conceptTypeTmp instanceof RegExpConceptType))  
			    return Result.ERROR_18;
			else {
			    if (!conceptTypeTmp.isExtrExpParsed) {
				if (isFullApply) {
				    Result resultTmp = ((RegExpConceptType)conceptTypeTmp).parseExtractingExp(conceptContainer,isFullApply);
				    if (resultTmp != Result.NO_ERROR) return resultTmp;
				} else
				    return Result.ERROR_19;
			    }
			    name.setLength(0);
			    regExp.append('('+((RegExpConceptType)conceptTypeTmp).regExp+')');
			    baseConceptTypes.addUniqueConceptType(conceptTypeTmp);
			    if ( (((RegExpConceptType)conceptTypeTmp).constraints != null) && (((RegExpConceptType)conceptTypeTmp).constraints.size()>0) ) {
				emptyConstraint = new ExtractiveConcept((RegExpConceptType)conceptTypeTmp, groupQntInRegExp);
				constraints.add(emptyConstraint);
			    }
			}
			// Attribute declaration is expected.
			if (ch == ' ') {
			    constraint = new ExtractiveConcept((RegExpConceptType)conceptTypeTmp, groupQntInRegExp);
			    state = 3;
			}
			// End of concept declaration in extracting expression
			else if (isConceptTypeBorder(ch)) {
			    groupQntInRegExp += ((RegExpConceptType)conceptTypeTmp).groupQntInRegExp;
			    endBorder = i;
			    state = 0;
			}
		    }
		    else
			return Result.ERROR_13;
		    break;
		case 3:
		    // First symbol of the attribute name
		    if (isAttributeNameSymbol(ch)) {
			attrName.append(ch);
			state = 4;
		    }
		    else
			return Result.ERROR_14;
		    break;
		case 4:
		    // Anther symbol of the attribute name
		    if (isAttributeNameSymbol(ch))
			attrName.append(ch);
		    // Attribute value is expected.
		    else if (ch == '=')
			state=5;
		    else
			return Result.ERROR_14;
		    break;
		case 5:
		    // Value of the attribute is begun to read
		    if (ch == '"')
			state = 6;
		    else
			return Result.ERROR_15;
		    break;
		case 6:
		    // Value of the attribute was readed
		    if (ch == '"') {
			Result res = constraint.setAttribute(attrName.toString(), attrValue.toString());
			if (res == Result.NO_ERROR) {
			    attrName.setLength(0); attrValue.setLength(0);
			    state = 7;
			}
			else return res;
		    }
		    // Values of the attribute
		    else 
			attrValue.append(ch);
		    break;
		case 7:
		    // Next attribute is expected
		    if (ch == ' ')
			state = 3;
		    // End of concept declaration in extracting expression
		    else if (isConceptTypeBorder(ch)) {
			groupQntInRegExp += ((RegExpConceptType)conceptTypeTmp).groupQntInRegExp;
			constraints.add(constraint);
			endBorder = i;
			state = 0;
		    }
		    else return Result.ERROR_23;
		    break;
	    }
	}
	if (state==0) {
	    // Deleting of parenthesis
	    if ( (baseConceptTypes.getConceptTypes().size() == 1)&&(startBorder == 0)&&(endBorder == extrExp.length()-1) ) {
		regExp = regExp.deleteCharAt(0); regExp = regExp.deleteCharAt(regExp.length()-1);
		if (constraint != null)
		    constraint.setGroupNum(0);
		if (emptyConstraint != null)
		    emptyConstraint.setGroupNum(0);
		groupQntInRegExp--;
	    }
	    constraint = getConstraint();
	    this.constraints = (constraints.size()>0)? constraints: null;
	    if (constraint != null) {
		if (this.constraints == null) this.constraints = new LinkedList();
		this.constraints.addFirst(constraint);
	    }
	    this.baseConceptTypes = (baseConceptTypes.getConceptTypes().size()>0)? baseConceptTypes.getConceptTypes(): null;
	    this.regExp = regExp.toString();
	    isExtrExpParsed = true;
	    this.groupQntInRegExp = groupQntInRegExp;
	    return Result.NO_ERROR;
	}
	else
	    return Result.ERROR_16;
    }
    
	/**
	 * 
	 * @param conceptContainer 
	 * @return Result
	 */
    final Result parseAllDerivedConceptTypes(ConceptTypeContainer conceptContainer) {
	Result res = Result.NO_ERROR;
	if (derivedConceptTypes == null) return res;
	LinkedList derivedConceptTypes2 = FuncLib.LinkedListClone(derivedConceptTypes);
        Iterator conceptTypeIter = derivedConceptTypes2.iterator();
        while (conceptTypeIter.hasNext()) {
	    res = ((RegExpConceptType)conceptTypeIter.next()).parseMeAndAllDerivedConceptTypes(conceptContainer);
	    if (res != Result.NO_ERROR)
		return res;
	}
	return res;
    }
    private final Result parseMeAndAllDerivedConceptTypes(ConceptTypeContainer conceptContainer) {
	if (isVisitedInRecursion)
	    return Result.ERROR_20;
	Result res = Result.NO_ERROR;
	if (!isExtrExpParsed) {
	    res = parseExtractingExp(conceptContainer, true);
	    isVisitedInRecursion = true;
	    if (res != Result.NO_ERROR)
		return res;
	}
	else
	    isVisitedInRecursion = true;
	if (derivedConceptTypes != null) {
	    LinkedList derivedConceptTypes2 = FuncLib.LinkedListClone(derivedConceptTypes);
	    Iterator conceptTypeIter = derivedConceptTypes2.iterator();
	    while (conceptTypeIter.hasNext()) {
		res = ((RegExpConceptType)conceptTypeIter.next()).parseMeAndAllDerivedConceptTypes(conceptContainer);
		if (res != Result.NO_ERROR)
		    return res;
	    }
	}
	isVisitedInRecursion = false;
	return res;
    }

    public final ConceptTypeStruct getStruct() {
	ConceptTypeStruct struct = super.getStruct();
	struct.conceptTypeId = ConceptTypeConst.REG_EXP_CONCEPT_TYPE;
	return struct;
    }

    public final String getRegExp() {
	return regExp;
    }

    public final int getGroupQntInRegExp() {
	return groupQntInRegExp;
    }

    public String toString() {
	StringBuffer str = new StringBuffer();
	str.append(super.toString()+"regular expression = "+regExp+"\ngroups = "+groupQntInRegExp+'\n');
	return str.toString();
    }

	/**
	 * Regular expression for extracting
	 */
    private String regExp = null;
	/**
	 * Quantity of the qroups in the regular expression (regExp member).
	 * It is necessary for derived concepts.
	 */
    private int groupQntInRegExp = 0;
}
